package com.panfeng.xcloud.common.security.aop.log;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.panfeng.xcloud.common.core.constants.GlobalConstant;
import com.panfeng.xcloud.common.core.thread.ThreadLocalMap;
import com.panfeng.xcloud.common.core.utils.RequestUtil;
import com.panfeng.xcloud.common.core.web.vo.ResponseVO;
import com.panfeng.xcloud.common.security.aop.log.dto.MdCallLogReqDto;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.concurrent.CompletableFuture;

/**
 *
 *  日志收集Aspect
 *
 * @author xiaobo
 * @version 1.0
 * @since 2017-04-10
 */
@Slf4j
@Aspect
@Component
public class LogCollectAspect {

    private ThreadLocal<Date> logTimeThreadLocal = new ThreadLocal<>();

    @Resource
    private RestTemplate restTemplate;

    private static final int DATA_MAX_SIZE = 2000;

    private static final String logUrl = "http://xcloud-mgr-provider-udc/api/log/saveCallLog";

    @Pointcut("@annotation(com.panfeng.xcloud.common.security.aop.log.LogCollectAnno)")
    public void logCollect() {
    }

    @Before("logCollect()")
    public void doBefore() {
        this.logTimeThreadLocal.set(new Date(System.currentTimeMillis()));
    }

    @AfterReturning(pointcut = "logCollect()", returning = "returnValue")
    public void doAfter(final JoinPoint joinPoint, final Object returnValue) {
        if (returnValue instanceof ResponseVO) {
            ResponseVO responseVO = (ResponseVO) returnValue;
            if (null != responseVO && ResponseVO.SUCCESS.equals(responseVO.getCode())) {
                this.handleLogData(joinPoint, responseVO);
            }
        }
    }

    private void handleLogData(final JoinPoint joinPoint, final Object responseVO) {
        final Date startTime = this.logTimeThreadLocal.get();
        final Date endTime = new Date(System.currentTimeMillis());
        HttpServletRequest request = RequestUtil.getRequest();
        String requestURI = request.getRequestURI();

        try {
            LogCollectAnno logCollectAnno = getLogCollectAnno(joinPoint);
            if (logCollectAnno == null) {
                return;
            }
            final String ipAddress = RequestUtil.getRemoteAddr(request);
            MdCallLogReqDto mdCallLogReqDto = new MdCallLogReqDto();

            if(null != ThreadLocalMap.get(GlobalConstant.Sys.TOKEN_AUTH_DTO)){
                String currentUserJson = JSON.toJSONString(ThreadLocalMap.get(GlobalConstant.Sys.TOKEN_AUTH_DTO));
                JSONObject currentUserJsonObject = JSONObject.parseObject(currentUserJson);
                mdCallLogReqDto.setExecutorId(currentUserJsonObject.getLongValue("userId"));
            }else{
                mdCallLogReqDto.setExecutorId(-1L);
            }

            mdCallLogReqDto.setClassName(joinPoint.getTarget().getClass().getName());
            mdCallLogReqDto.setMethodName(joinPoint.getSignature().getName());
            mdCallLogReqDto.setExecuteTime(endTime.getTime() - startTime.getTime());
            mdCallLogReqDto.setStartTime(startTime);
            mdCallLogReqDto.setEndTime(endTime);
            mdCallLogReqDto.setIp(ipAddress);
            mdCallLogReqDto.setRequestUrl(requestURI);
            mdCallLogReqDto.setDescription(logCollectAnno.description());

            getAndSetMethodData(logCollectAnno, mdCallLogReqDto, responseVO, joinPoint);
            logTimeThreadLocal.remove();
            CompletableFuture.runAsync(()->{
                try {
                    ResponseVO logResponse = this.restTemplate.postForObject(logUrl, mdCallLogReqDto, ResponseVO.class);
                    log.info(">>> 记录日志状态：{}",JSON.toJSONString(logResponse));
                }catch (Exception e){
                    log.error("请求日志中心服务异常={}", e.getMessage(), e);
                    e.printStackTrace();
                }
            });
        } catch (Exception ex) {
            log.error("获取日志信息出现异常={}", ex.getMessage(), ex);
        }
    }

    private void getAndSetMethodData(LogCollectAnno logCollectAnno, MdCallLogReqDto mdCallLogReqDto, Object responseVO, JoinPoint joinPoint) {
        if (!logCollectAnno.ignoreReqData()) {
            setReqData(mdCallLogReqDto, joinPoint);
        }
        if (!logCollectAnno.ignoreRespData()) {
            setRespData(mdCallLogReqDto, responseVO);
        }
    }

    private void setRespData(MdCallLogReqDto mdCallLogReqDto, Object responseVO) {
        try {
            String responseJsonString = JSON.toJSONString(responseVO);
            mdCallLogReqDto.setResponseData(responseJsonString);
        } catch (Exception e) {
            log.error("获取日志响应数据,出现错误={}", e.getMessage(), e);
        }
    }

    private void setReqData(MdCallLogReqDto mdCallLogReqDto, JoinPoint joinPoint) {
        try {
            Object[] args = joinPoint.getArgs();
            if (args.length == 0) {
                return;
            }
            Object[] parameter = new Object[args.length];
            int index = 0;
            for (Object object : parameter) {
                if (object instanceof HttpServletRequest) {
                    continue;
                }
                parameter[index] = object;
                index++;
            }
            String requestData = JSON.toJSONString(parameter);
            if (requestData.length() > DATA_MAX_SIZE) {
                requestData = requestData.substring(DATA_MAX_SIZE);
            }
            mdCallLogReqDto.setRequestData(requestData);
        } catch (Exception e) {
            log.error("获取请求数据,出现错误={}", e.getMessage(), e);
        }
    }

    private static LogCollectAnno getLogCollectAnno(JoinPoint joinPoint) {
        Method[] methods = joinPoint.getTarget().getClass().getDeclaredMethods();
        String methodName = joinPoint.getSignature().getName();
        if (null != methods && 0 < methods.length) {
            for (Method met : methods) {
                LogCollectAnno logCollectAnno = met.getAnnotation(LogCollectAnno.class);
                if (null != logCollectAnno && methodName.equals(met.getName())) {
                    return logCollectAnno;
                }
            }
        }
        return null;
    }

}
